/*
 * (c) Copyright Ascensio System SIA 2010-2024
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-6 Ernesta Birznieka-Upish
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

"use strict";

(function (window, undefined) {

	function CPoint(x, y, bTemporary) {
		this.x = x;
		this.y = y;
		this.bTemporary = bTemporary === true;
	}

	CPoint.prototype.reset = function (x, y, bTemporary) {
		this.x = x;
		this.y = y;
		this.bTemporary = bTemporary === true;
	};
	CPoint.prototype.distance = function (x, y) {
		var dx = this.x - x;
		var dy = this.y - y;
		return Math.sqrt(dx * dx + dy * dy);
	};
	CPoint.prototype.distanceFromOther = function (oPoint) {
		return this.distance(oPoint.x, oPoint.y);
	};
	CPoint.prototype.isNear = function (x, y) {
		return this.distance(x, y) < 1;
	};
	CPoint.prototype.serialize = function (w) {
		w.WriteDouble(this.x);
		w.WriteDouble(this.y);
        w.WriteBool(this.bTemporary);
	};
	CPoint.prototype.deserialize = function (r) {
		this.x = r.GetDouble();
		this.y = r.GetDouble();
        this.bTemporary = r.GetBool();
	};

	function PolyLine(drawingObjects, theme, master, layout, slide, pageIndex) {

		AscFormat.ExecuteNoHistory(function () {

			this.drawingObjects = drawingObjects;
			this.arrPoint = [];
			this.Matrix = new AscCommon.CMatrix();
			this.TransformMatrix = new AscCommon.CMatrix();

			this.pageIndex = pageIndex;
			this.style = AscFormat.CreateDefaultShapeStyle();
			var style = this.style;
			style.fillRef.Color.Calculate(theme, slide, layout, master, {R: 0, G: 0, B: 0, A: 255});
			var RGBA = style.fillRef.Color.RGBA;
			var pen = theme.getLnStyle(style.lnRef.idx, style.lnRef.Color);
			style.lnRef.Color.Calculate(theme, slide, layout, master);
			RGBA = style.lnRef.Color.RGBA;

			const API = Asc.editor || editor;
			const bInkDraw = API.isInkDrawerOn();
			this.bInk = bInkDraw;
			if (bInkDraw) {
				pen = API.getInkPen();
			}
			if (pen.Fill) {
				pen.Fill.calculate(theme, slide, layout, master, RGBA);
			}


			this.pen = pen;

			this.polylineForDrawer = new PolylineForDrawer(this);
			this.continuousRanges = [];
			let oAnnot = Asc.editor.getAnnotations();
			if(oAnnot) {
				oAnnot.onCreatePolylineTrack(this, drawingObjects.drawingObjects);
			}
		}, this, []);
	}

	PolyLine.prototype.Draw = function (graphics) {

		graphics.SetIntegerGrid(false);
		graphics.transform3(this.Matrix);

		const oShapeDrawer = new AscCommon.CShapeDrawer();
		oShapeDrawer.fromShape(this, graphics);
		oShapeDrawer.draw(this);
	};
	PolyLine.prototype.draw = function (oDrawer) {
		if (AscFormat.isRealNumber(this.pageIndex) && oDrawer.SetCurrentPage) {
			oDrawer.SetCurrentPage(this.pageIndex);
		}
		const oGraphics = oDrawer.Graphics || oDrawer;
		const API = Asc.editor || editor;
		const bInkDraw = API.isInkDrawerOn();
		const dOldAlpha = oGraphics.globalAlpha;
		if (bInkDraw) {
			if (AscFormat.isRealNumber(oGraphics.globalAlpha) && oGraphics.put_GlobalAlpha) {
				oGraphics.put_GlobalAlpha(false, 1);
			}
		}
		this.polylineForDrawer.Draw(oDrawer);

		if (AscFormat.isRealNumber(dOldAlpha) && oGraphics.put_GlobalAlpha) {
			oGraphics.put_GlobalAlpha(true, dOldAlpha);
		}
	};

	PolyLine.prototype.getBounds = function () {
		var boundsChecker = new AscFormat.CSlideBoundsChecker();
		this.draw(boundsChecker);
		boundsChecker.Bounds.posX = boundsChecker.Bounds.min_x;
		boundsChecker.Bounds.posY = boundsChecker.Bounds.min_y;
		boundsChecker.Bounds.extX = boundsChecker.Bounds.max_x - boundsChecker.Bounds.min_x;
		boundsChecker.Bounds.extY = boundsChecker.Bounds.max_y - boundsChecker.Bounds.min_y;
		return boundsChecker.Bounds;
	};
	PolyLine.prototype.getShape = function (bWord, drawingDocument, drawingObjects) {
		var xMax = this.arrPoint[0].x, yMax = this.arrPoint[0].y, xMin = xMax, yMin = yMax;
		var i;

		var bClosed = false;
		var min_dist;
		if (drawingObjects) {
			min_dist = drawingObjects.convertPixToMM(3);
		}
		else {
			min_dist = editor.WordControl.m_oDrawingDocument.GetMMPerDot(3)
		}
		var oLastPoint = this.arrPoint[this.arrPoint.length - 1];
		var nLastIndex = this.arrPoint.length - 1;
		if (oLastPoint.bTemporary) {
			nLastIndex--;
		}
		if (nLastIndex > 1) {
			var dx = this.arrPoint[0].x - this.arrPoint[nLastIndex].x;
			var dy = this.arrPoint[0].y - this.arrPoint[nLastIndex].y;
			if (Math.sqrt(dx * dx + dy * dy) < min_dist) {
				bClosed = true;
			}
		}
		if (this.bInk) {
			bClosed = false;
		}
		var nMaxPtIdx = bClosed ? (nLastIndex - 1) : nLastIndex;
		for (i = 1; i <= nMaxPtIdx; ++i) {
			if (this.arrPoint[i].x > xMax) {
				xMax = this.arrPoint[i].x;
			}
			if (this.arrPoint[i].y > yMax) {
				yMax = this.arrPoint[i].y;
			}

			if (this.arrPoint[i].x < xMin) {
				xMin = this.arrPoint[i].x;
			}

			if (this.arrPoint[i].y < yMin) {
				yMin = this.arrPoint[i].y;
			}
		}

		let shape = this.drawingObjects.createShape();

		//  if(drawingObjects)
		//  {
		//      shape.setWorksheet(drawingObjects.getWorksheetModel());
		//      shape.addToDrawingObjects();
		//  }
		shape.setSpPr(new AscFormat.CSpPr());
		shape.spPr.setParent(shape);
		shape.spPr.setXfrm(new AscFormat.CXfrm());
		shape.spPr.xfrm.setParent(shape.spPr);
		if (!bWord) {
			shape.spPr.xfrm.setOffX(xMin);
			shape.spPr.xfrm.setOffY(yMin);
		}
		else {
			shape.setWordShape(true);
			shape.spPr.xfrm.setOffX(0);
			shape.spPr.xfrm.setOffY(0);
		}
		shape.spPr.xfrm.setExtX(xMax - xMin);
		shape.spPr.xfrm.setExtY(yMax - yMin);
		shape.setStyle(AscFormat.CreateDefaultShapeStyle());
		if (this.bInk) {
			shape.spPr.setLn(this.pen);
			shape.spPr.setFill(AscFormat.CreateNoFillUniFill());
		}
		var geometry = new AscFormat.Geometry();


		var w = xMax - xMin, h = yMax - yMin;
		var kw, kh, pathW, pathH;
		if (w > 0) {
			pathW = 43200;
			kw = 43200 / w;
		}
		else {
			pathW = 0;
			kw = 0;
		}
		if (h > 0) {
			pathH = 43200;
			kh = 43200 / h;
		}
		else {
			pathH = 0;
			kh = 0;
		}
		geometry.AddPathCommand(0, undefined, bClosed ? "norm" : "none", undefined, pathW, pathH);
		geometry.AddRect("l", "t", "r", "b");
		geometry.AddPathCommand(1, (((this.arrPoint[0].x - xMin) * kw) >> 0) + "", (((this.arrPoint[0].y - yMin) * kh) >> 0) + "");
		i = 1;
		var aRanges = this.continuousRanges;
		var aRange, nRange;
		var nEnd;
		var nPtsCount = this.arrPoint.length;
		var oPt1, oPt2, oPt3, nPt;
		for (nRange = 0; nRange < aRanges.length; ++nRange) {
			aRange = aRanges[nRange];
			if (aRange[0] + 1 > nMaxPtIdx) {
				break;
			}
			nPt = aRange[0] + 1;
			nEnd = Math.min(aRange[1], nMaxPtIdx);
			while (nPt <= nEnd) {
				if (nPt + 2 <= nEnd) {
					//cubic bezier curve
					oPt1 = this.arrPoint[nPt++];
					oPt2 = this.arrPoint[nPt++];
					oPt3 = this.arrPoint[nPt++];
					geometry.AddPathCommand(5, (((oPt1.x - xMin) * kw) >> 0) + "", (((oPt1.y - yMin) * kh) >> 0) + "", (((oPt2.x - xMin) * kw) >> 0) + "", (((oPt2.y - yMin) * kh) >> 0) + "", (((oPt3.x - xMin) * kw) >> 0) + "", (((oPt3.y - yMin) * kh) >> 0) + "");
				}
				else if (nPt + 1 <= nEnd) {
					//quad bezier curve
					oPt1 = this.arrPoint[nPt++];
					oPt2 = this.arrPoint[nPt++];
					geometry.AddPathCommand(4, (((oPt1.x - xMin) * kw) >> 0) + "", (((oPt1.y - yMin) * kh) >> 0) + "", (((oPt2.x - xMin) * kw) >> 0) + "", (((oPt2.y - yMin) * kh) >> 0) + "");
				}
				else {
					//lineTo
					oPt1 = this.arrPoint[nPt++];
					geometry.AddPathCommand(2, (((oPt1.x - xMin) * kw) >> 0) + "", (((oPt1.y - yMin) * kh) >> 0) + "");
				}
			}
		}
		if (bClosed) {
			geometry.AddPathCommand(6);
		}

		shape.spPr.setGeometry(geometry);
		shape.setBDeleted(false);
		shape.recalculate();
		shape.x = xMin;
		shape.y = yMin;
		return shape;
	};
	PolyLine.prototype.checkAnnotationChanges = function () {
		let oAnnot = Asc.editor.getAnnotations();
		if(oAnnot) {
			oAnnot.onPolylineTrackChanged(this);
		}
	};
	PolyLine.prototype.tryAddPoint = function (x, y) {
		var oLastPoint = this.arrPoint[this.arrPoint.length - 1];
		if (!oLastPoint) {
			this.addPoint(x, y);
			this.checkAnnotationChanges();
			return;
		}
		if (oLastPoint.isNear(x, y)) {
			//oLastPoint.reset(x, y);
			return;
		}
		this.addPoint(x, y);
		this.checkAnnotationChanges();
	};

	PolyLine.prototype.createContinuousRange = function () {
		var nIdx = this.arrPoint.length - 1;
		this.continuousRanges.push([nIdx, nIdx]);
	};
	PolyLine.prototype.getLastContinuousRange = function () {
		if (this.continuousRanges.length === 0) {
			this.createContinuousRange();
		}
		return this.continuousRanges[this.continuousRanges.length - 1];
	};
	PolyLine.prototype.addPoint = function (x, y, bTemporary) {
		this.arrPoint.push(new CPoint(x, y, bTemporary));
		let oLastRange = this.getLastContinuousRange();
		oLastRange[1] = this.arrPoint.length - 1;

	};
	PolyLine.prototype.replaceLastPoint = function (x, y, bTemporary) {
		let oLastPoint = this.arrPoint[this.arrPoint.length - 1];
		if (!oLastPoint) {
			this.addPoint(x, y, bTemporary);
			this.checkAnnotationChanges();
			return;
		}
		oLastPoint.reset(x, y, bTemporary);
		let oLastRange = this.getLastContinuousRange();
		if (oLastRange[0] !== this.arrPoint.length - 1) {
			this.createContinuousRange();
		}
		this.checkAnnotationChanges();
	};
	PolyLine.prototype.canCreateShape = function () {
		var nCount = this.arrPoint.length;
		if (nCount < 2) {
			return false;
		}
		var oLast = this.arrPoint[this.arrPoint.length - 1];
		if (oLast.bTemporary) {
			--nCount;
		}
		return nCount > 1;
	};
	PolyLine.prototype.getPointsCount = function () {
		return this.arrPoint.length;
	};
	PolyLine.prototype.serialize = function (w) {
        let nCount = this.arrPoint.length;
		w.WriteLong(nCount);
        for(let nPt = 0; nPt < nCount; ++nPt) {
            this.arrPoint[nPt].serialize(w);
        }
		let nRangesCount = this.continuousRanges.length;
		w.WriteLong(nRangesCount);
		for(let nRange = 0; nRange < nRangesCount; ++nRange) {
			let oRange = this.continuousRanges[nRange];
			w.WriteLong(oRange[0]);
			w.WriteLong(oRange[1]);
		}
	};
	PolyLine.prototype.deserialize = function (r) {
		this.arrPoint.length = 0;
		this.continuousRanges.length = 0;
        let nCount = r.GetLong();
        for(let nPt = 0; nPt < nCount; ++nPt) {
            let oPt = new CPoint();
			oPt.deserialize(r);
			this.arrPoint.push(oPt);
        }
		let nRangesCount = r.GetLong();
		for(let nRange = 0; nRange < nRangesCount; ++nRange) {
			let oRange = [];
			oRange[0] = r.GetLong();
			oRange[1] = r.GetLong();
			this.continuousRanges.push(oRange);
		}
	};

	function PolylineForDrawer(polyline) {
		this.polyline = polyline;
		this.pen = polyline.pen;
		this.brush = polyline.brush;
		this.TransformMatrix = polyline.TransformMatrix;
		this.Matrix = polyline.Matrix;

		this.Draw = function (graphics) {
			graphics.SetIntegerGrid(false);
			graphics.transform3(this.Matrix);

			const shape_drawer = new AscCommon.CShapeDrawer();
			shape_drawer.fromShape(this, graphics);
			shape_drawer.draw(this);
		};
		this.draw = function (g) {
			g._e();
			if (this.polyline.arrPoint.length < 2) {
				return;
			}
			g._m(this.polyline.arrPoint[0].x, this.polyline.arrPoint[0].y);
			for (var i = 1; i < this.polyline.arrPoint.length; ++i) {
				g._l(this.polyline.arrPoint[i].x, this.polyline.arrPoint[i].y);
			}
			g.ds();
		};
	}

	//--------------------------------------------------------export----------------------------------------------------
	window['AscFormat'] = window['AscFormat'] || {};
	window['AscFormat'].PolyLine = PolyLine;
})(window);
